package com.tgjorgoski.sidekicknotes.rtfview.support;

import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.undo.*;

import org.apache.log4j.Logger;

public class UndoRedoSupport implements UndoableEditListener, PropertyChangeListener {

	private static Logger log = Logger.getLogger(UndoRedoSupport.class);
	
	public static final String UNDO_ACTION="Undo", REDO_ACTION="Redo";
	protected UndoAction undoAction;
	protected RedoAction redoAction;
	protected UndoManager undo = new UndoManager();
	private Document oldDocument;
	
	private static UndoRedoSupport instance;
	
	public static UndoRedoSupport getInstance() {
		if (instance==null) {
			instance=new UndoRedoSupport();
		}
		return instance;
	}

	private UndoRedoSupport() {
		undoAction=new UndoAction();
		redoAction=new RedoAction();
	}
	
	public void registerComponent(JTextPane component) {
		Document doc = component.getDocument();
		component.getActionMap().put(UNDO_ACTION, undoAction);
		component.getActionMap().put(REDO_ACTION, redoAction);
		
		//listen for loading of another page, so that we can attach to the new document 
		component.addPropertyChangeListener("page", this);
		
		doc.addUndoableEditListener(this);
		oldDocument=doc;
	}
	
	public Action getUndoAction() {
		return undoAction;
	}
	public Action getRedoAction() {
		return redoAction;
	}
	
	// This one listens for edits that can be undone.
	public void undoableEditHappened(UndoableEditEvent e) {
		// Remember the edit and update the menus.
		undo.addEdit(e.getEdit());
		undoAction.updateUndoState();
		redoAction.updateRedoState();
	}
	
	class UndoAction extends AbstractAction {
		public UndoAction() {
			super("Undo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				undo.undo();
			} catch (CannotUndoException ex) {
				log.error("Error while undo", ex);
			}
			updateUndoState();
			redoAction.updateRedoState();
		}

		protected void updateUndoState() {
			if (undo.canUndo()) {
				setEnabled(true);
				putValue(Action.NAME, undo.getUndoPresentationName());
			} else {
				setEnabled(false);
				putValue(Action.NAME, "Undo");
			}
		}
	}

	class RedoAction extends AbstractAction {
		public RedoAction() {
			super("Redo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				undo.redo();
			} catch (CannotRedoException ex) {
				log.error("Error while redo", ex);
			}
			updateRedoState();
			undoAction.updateUndoState();
		}

		protected void updateRedoState() {
			if (undo.canRedo()) {
				setEnabled(true);
				putValue(Action.NAME, undo.getRedoPresentationName());
			} else {
				setEnabled(false);
				putValue(Action.NAME, "Redo");
			}
		}
	}

	public void switchToNewDocument(Document doc) {
		oldDocument.removeUndoableEditListener(this);
		undo.discardAllEdits();
		doc.addUndoableEditListener(this);
		undoAction.updateUndoState();
		redoAction.updateRedoState();
	}

	public void propertyChange(PropertyChangeEvent evt) {
		if (evt.getSource() instanceof JTextComponent) {
			JTextComponent tc = (JTextComponent)evt.getSource();
			switchToNewDocument(tc.getDocument());
		}
	}
}
